Respond with 404 Not Found if project not found in Api::SchoolProjectsController actions#564
Merged
floehopper merged 1 commit intomainfrom Jul 2, 2025
Conversation
Previously when a request was made to either of the actions in
`Api::SchoolProjectsController` for a project that does not exist, the
following exception was raised:
NoMethodError: undefined method `school_project' for nil:NilClass
Unhandled exceptions are rescued by the default
`ActionDispatch::ShowExceptions` middleware [1]. In turn this uses the
default exceptions app, `ActionDispatch::PublicExceptions` [2] to render
a static HTML error page from `public/500.html` in this case.
Since this is an API endpoint, ideally it should always respond with
JSON in the response body, otherwise it makes writing client code a lot
harder. Indeed my actual motivation for fixing this is, because we've
been seeing JSON:ParseError exceptions raised in experience-cs [3].
I've fixed the problem by using `Project.find_by!` instead of
`Project.find_by`. This means that if the project is not found, a
`ActiveRecord::RecordNotFound` exception is raised. This in turn is
rescued by the `rescue_from` block in `ApiController` [4] and the action
responds with a 404 Not Found head response, i.e. with no body.
This head response seems to be handled OK by the HTTParty code in
experience-cs [5] and thus it fixes the problem I was trying to address.
The equivalent client-side code in editor-standalone [6] was already
throwing an AxisError exception due to the 500 response code (not being
a 2XX code [7]) and it will continue to throw the same exception, albeit
with a payload reflecting the response code being 404 instead of 500 and
the body being empty instead of being the Rails 500 HTML error page.
There is no specific error-handling code in editor-standalone catching
this exception, so I assume it's just relying on the generic error
handling for the React app. Thus I think the user-facing behaviour
should be unchanged.
[1]: https://api.rubyonrails.org/v7.1.3.4/classes/ActionDispatch/ShowExceptions.html
[2]: https://api.rubyonrails.org/v7.1.3.4/classes/ActionDispatch/PublicExceptions.html
[3]: https://raspberrypifoundation.sentry.io/issues/6708738500/
[4]: https://github.com/RaspberryPiFoundation/editor-api/blob/c37ab30714edeb08beaa1929970772545f38e93d/app/controllers/api_controller.rb#L9
[5]: https://github.com/RaspberryPiFoundation/experience-cs/blob/7b559bef916e0a32b3174f2391dcc587a8c4fe3b/lib/editor_api/client.rb
[6]: https://github.com/RaspberryPiFoundation/editor-standalone/blob/f1286121460e79da7ffa7431487a2fb0872f2ae5/src/utils/apiCallHandler/projects.js#L71-L91
[7]: https://axios-http.com/docs/handling_errors
sebjacobs
approved these changes
Jul 2, 2025
Contributor
There was a problem hiding this comment.
❤️ Thanks @floehopper. It's great that you've been able to wrap some simple specs around this bug. I'm also really impressed how you've managed to avoid scope creep here.
Contributor
Author
|
Adrian has given me the go-ahead to merge this. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Previously when a request was made to either of the actions in
Api::SchoolProjectsControllerfor a project that does not exist, the following exception was raised, because the call toProject.find_byreturnednil:Unhandled exceptions are rescued by the default
ActionDispatch::ShowExceptionsmiddleware. In turn this uses the default exceptions app,ActionDispatch::PublicExceptionsto render a static HTML error page frompublic/500.htmlin this case.Since this is an API endpoint, ideally it should always respond with JSON in the response body, otherwise it makes writing client code a lot harder. Indeed my actual motivation for fixing this is, because we've been seeing
JSON:ParseErrorexceptions raised in experience-cs.I've fixed the problem by using
Project.find_by!instead ofProject.find_by. This means that if the project is not found, aActiveRecord::RecordNotFoundexception is raised. This in turn is rescued by therescue_fromblock inApiControllerand the action responds with a404 Not Foundhead response, i.e. with no body.This head response seems to be handled OK by the HTTParty code in experience-cs and thus it fixes the problem I was trying to address.
The equivalent client-side code in editor-standalone was already throwing an
AxiosErrorexception due to the500response code (not being a2XXcode) and it will continue to throw the same exception, albeit with a payload reflecting the response code being404instead of500and the body being empty instead of being the Rails500HTML error page.There is no specific error-handling code in editor-standalone catching this exception, so I assume it's just relying on the generic error handling for the React app. Thus I think the user-facing behaviour should be unchanged.
Addresses https://github.com/RaspberryPiFoundation/experience-cs/issues/828.
Postscript
While investigating this issue, I've uncovered a number of other problems with the
Api::SchoolProjectsControllercode and its specs, but I've intentionally not addressed them here in order to keep the scope of this PR as small as possible and keep it focussed on fixing the problem at hand. However, in case it's useful, I'll list the issues here:load_and_authorize_resource :projectis not working as intended:Projectinstance (even if it was, it would be attempting to callProject.find(params[:id)and notProject.find_by!(identifier: params[:id])Project(it seems to be authorizing the user canshowtheProjectclass, but it seems unlikely this is what was intended)load_and_authorize_resourcewas added and no tests fail if I remove it!Api::ProjectsControlleruses a customProjectLoaderclass which takeslocaleinto account, I would've thought that thisApi::SchoolProjectsControllershould also use the same lookup mechanism, but it doesn't.before_action :authorize_usercall.ProfileApiMock#stub_profile_api_list_school_studentsinspec/requests/school_projects/show_finished_spec.rb&spec/requests/school_projects/set_finished_spec.rbseem unnecessary and are potentially confusing. This helper method stubsProfileApiClient.list_school_students, but this method is never called by code exercised by those specs. This seems to be a wider problem where calls toProfileApiMock#stub_profile_api_list_school_studentshave been added to specs that don't need it. Perhaps they never needed it or the need for them has gone away.lib/concepts/*/operations/are usually rescued and the action responds with a JSON body. However, for exceptions that occur outside those operations (e.g. in the actions themselves) just result in the 500 error with HTML body.